package org.yeasy.cud.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 53 bits unique id:
 * <p>
 * |--------|--------|--------|--------|--------|--------|--------|--------|
 * |00000000|00011111|11111111|11111111|11111111|11111111|11111111|11111111|
 * |--------|---xxxxx|xxxxxxxx|xxxxxxxx|xxxxxxxx|xxx-----|--------|--------|
 * |--------|--------|--------|--------|--------|---xxxxx|xxxxxxxx|xxx-----|
 * |--------|--------|--------|--------|--------|--------|--------|---xxxxx|
 * <p>
 * Maximum ID = 11111_11111111_11111111_11111111_11111111_11111111_11111111
 * <p>
 * Maximum TS = 11111_11111111_11111111_11111111_111
 * <p>
 * Maximum NT = ----- -------- -------- -------- ---11111_11111111_111 = 65535
 * <p>
 * Maximum SH = ----- -------- -------- -------- -------- -------- ---11111 = 31
 * <p>
 * It can generate 64k unique id per IP and up to 2106-02-07T06:28:15Z.
 * ps:将epoch+next+shardId的顺序,改为shardId+epoch+next
 *
 * @author YangYishe
 * @date 2021年03月31日 10:47
 */
public final class IdUtil {

    private static final Logger logger = LoggerFactory.getLogger(IdUtil.class);

    private static final Pattern PATTERN_LONG_ID = Pattern.compile("^([0-9]{15})([0-9a-f]{32})([0-9a-f]{3})$");

    private static final Pattern PATTERN_HOSTNAME = Pattern.compile("^.*\\D+([0-9]+)$");

    /**
     * 原始偏移(偏移初始值)
     */
    private static final long OFFSET = LocalDate.of(2000, 1, 1).atStartOfDay(ZoneId.of("Z")).toEpochSecond();

    /**
     * (每个设备)每秒最多可以生成多少个id(共计65k个,2^16)
     */
    private static final long MAX_NEXT = 0b11111_11111111_111L;

    /**
     * 碎片id(服务器设备id)
     * 将此处根据设备取值,改为根据分系统取值
     * 暂定为:
     * cf-service:  0b00010L    2
     * cf-api:      0b00011L    3
     * fnc-service: 0b00100L    4
     * fnc-api:     0b00101L    5
     * pss-service: 0b01000L    8
     * pss-api:     0b01001L    9
     * pss-jn-ser:  0b01010L    10
     * pss-jn-api:  0b01011L    11
     */
    private static final long SHARD_ID = 0b10001L;
//    private static final long SHARD_ID = getServerIdAsLong();

    /**
     * 一个周期内的id偏移量
     */
    private static long offset = 0;

    /**
     * 上次获取id的周期(用以获取id的最小周期)
     */
    private static long lastEpoch = 0;

    /**
     * Next id long.
     *
     * @return the long
     * @author YangYishe
     * @date 2021年03月31日 10:47
     */
    public static Long nextId() {
        return nextBatchId(System.currentTimeMillis() / 1000,1L).get(0);
    }


    /**
     * Next batch id list.
     *
     * @param lngIdNeedCount the lng id need count
     * @return the list
     * @author YangYishe
     * @date 2021年03月31日 11:21
     */
    public static List<Long> nextBatchId(long lngIdNeedCount){
        return nextBatchId(System.currentTimeMillis()/1000,lngIdNeedCount);
    }

    /**
     * 下一个id
     * 此方法禁用,否则容易和nextBatchId产生冲突!!!
     * @param epochSecond 周期,以秒为单位
     * @return 获取到下一个id
     */
    private static synchronized long nextId(long epochSecond) {
        //当参数周期提前于上次调用周期时,将本次周期改为上次调用周期
        if (epochSecond < lastEpoch) {
            // warning: clock is turn back:
            logger.warn("clock is back: " + epochSecond + " from previous:" + lastEpoch);
            epochSecond = lastEpoch;
        }
        //如果上次周期与本次周期不同,则将上次周期改为本次周期,同时重置offset(偏移)
        if (lastEpoch != epochSecond) {
            lastEpoch = epochSecond;
            reset();
        }
        //偏移增加,并检查是否超出最大可调用id
        offset++;
        long next = offset & MAX_NEXT;
        //如果超过,则使用下一秒作为周期去获取id
        if (next == 0) {
            logger.warn("maximum id reached in 1 second in epoch: " + epochSecond);
            return nextId(epochSecond + 1);
        }
        //根据周期(秒),偏移,设备id,生成下一个id
        return generateId(epochSecond, next, SHARD_ID);
    }


    /**
     * 下一批id
     * @param epochSecond 周期,以秒为单位
     * @param lngIdNeedCount 要生成的id数量
     * @return 生成的批次id
     */
    private static synchronized List<Long> nextBatchId(long epochSecond,long lngIdNeedCount){
        //1.确定开始周期(lastEpoch)与偏移(offset)
        //当参数周期提前于上次调用周期时,将本次周期改为上次调用周期
        if (epochSecond < lastEpoch) {
            // warning: clock is turn back:
            logger.warn("clock is back: " + epochSecond + " from previous:" + lastEpoch);
            epochSecond = lastEpoch;
        }
        //如果上次周期与本次周期不同,则将上次周期改为本次周期,同时重置offset(偏移)
        if (lastEpoch != epochSecond) {
            lastEpoch = epochSecond;
            reset();
        }

        //2.判断是否需要多个周期(在当期能否解决所有问题)
        long lngIdLastCount=MAX_NEXT-offset;
        boolean blnInOneEpoch=lngIdLastCount>lngIdNeedCount;

        List<Long> lstIdResult=new ArrayList<>();
        if(blnInOneEpoch){
            //2.1.当在一个周期内能生成所有所需id
            for (long i = 0; i < lngIdNeedCount; i++) {
                offset++;
                lstIdResult.add(generateId(lastEpoch,offset,SHARD_ID));
            }
        }else{
            //2.2.当在一个周期内必定不能生成所有所需id
            lstIdResult.addAll(generateOldBatchId(lastEpoch,SHARD_ID));

            long lngIdNeedCountOld=lngIdNeedCount-lngIdLastCount;
            long lngEpochCount=lngIdNeedCountOld>>>16;
            long lngIdNeedCountNew=lngIdNeedCountOld&MAX_NEXT;
            if(lngEpochCount>0){
                for (long i = 0; i < lngEpochCount; i++) {
                    lstIdResult.addAll(generateWholeEpochId(++lastEpoch,SHARD_ID));
                }
            }
            if(lngIdNeedCountNew>0){
                lstIdResult.addAll(generateNewBatchId(++lastEpoch,SHARD_ID,lngIdNeedCountNew));
            }
        }

        return lstIdResult;
    }

    /**
     * 重置偏移
     */
    private static void reset() {
        offset = 0;
    }

    /**
     * 生成id(原始方法)
     * @param epochSecond 周期(秒)
     * @param next 偏移
     * @param shardId 设备id
     * @return 返回计算的id
     */
    private static long generateId(long epochSecond, long next, long shardId) {
        return ((epochSecond - OFFSET) << 21) | (next << 5) | shardId;
//        return shardId<<48|((epochSecond - OFFSET) << 16) | next ;
    }

    /**
     * 得到旧批次id
     * @param epochSecond 周期(秒)
     * @param shardId 设备id
     * @return 返回计算的id集合
     */
    private static List<Long> generateOldBatchId(long epochSecond,long shardId){
        List<Long> lstId=new ArrayList<>();
        for(long i=offset;i<MAX_NEXT;i++){
            offset++;
            lstId.add(generateId(epochSecond,i,shardId));
        }
        reset();
        return lstId;
    }

    /**
     * 得到整周期的id
     * @param epochSecond 周期(秒)
     * @param shardId 设备id
     * @return 返回计算的id集合
     */
    private static List<Long> generateWholeEpochId(long epochSecond,long shardId){
        List<Long> lstId=new ArrayList<>();
        for (long i = 0; i < MAX_NEXT; i++) {
            lstId.add(generateId(epochSecond,i,shardId));
        }
        reset();
        return lstId;
    }

    /**
     * 得到新批次id
     * @param epochSecond 周期(秒)
     * @param shardId 设备id
     * @return 返回计算的id集合
     */
    private static List<Long> generateNewBatchId(long epochSecond,long shardId,long lngIdNeed){
        List<Long> lstId=new ArrayList<>();
        for (long i = 0; i < lngIdNeed; i++) {
            lstId.add(generateId(epochSecond,i,shardId));
        }
        offset=lngIdNeed;
        return lstId;
    }


    /**
     * 根据服务器设备,获取设备代表id
     * 此处不必了解的太详细,只需了解当服务器设备不同时,此处生成的服务器id不相同
     * @return 设备代表id
     */
    private static long getServerIdAsLong() {
        //对于分布式项目,此处数值的设置有两种方案
        //1.使用redis记录以上数据
        //2.手动区分两个设备
        try {
            String hostname = InetAddress.getLocalHost().getHostName();
            logger.info("host name:{}",hostname);
            Matcher matcher = PATTERN_HOSTNAME.matcher(hostname);
            if (matcher.matches()) {
                long n = Long.parseLong(matcher.group(1));
                if (n >= 0 && n < 8) {
                    logger.info("detect server id from host name {}: {}.", hostname, n);
                    return n;
                }
            }
        } catch (UnknownHostException e) {
            logger.warn("unable to get host name. set server id = 0.");
        }
        return 0;
    }

    /**
     * String id to long id long.
     * 将字符串id转化为long类型id
     * 但用不太到,故先屏蔽
     *
     * @param stringId the string id
     * @return the long
     * @author YangYishe
     * @date 2021年03月31日 10:47
     */
//    public static long stringIdToLongId(String stringId) {
//        // a stringId id is composed as timestamp (15) + uuid (32) + serverId (000~fff).
//        Matcher matcher = PATTERN_LONG_ID.matcher(stringId);
//        if (matcher.matches()) {
//            long epoch = Long.parseLong(matcher.group(1)) / 1000;
//            String uuid = matcher.group(2);
//            byte[] sha1 = HashUtil.sha1AsBytes(uuid);
//            long next = ((sha1[0] << 24) | (sha1[1] << 16) | (sha1[2] << 8) | sha1[3]) & MAX_NEXT;
//            long serverId = Long.parseLong(matcher.group(3), 16);
//            return generateId(epoch, next, serverId);
//        }
//        throw new IllegalArgumentException("Invalid id: " + stringId);
//    }

    /**
     * Main.
     *
     * @param args the args
     * @author YangYishe
     * @date 2021年03月31日 10:47
     */
    public static void main(String[] args){
        logger.info("MAX_NEXT:{}",nextBatchId(10));
    }
}